package dcore.id;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.SocketException;

/**
 * 根据Snowflake算法原理，时间码为时间戳，12位序号码，一台服务器每秒可产生4096*1000=409.6万IP，暂时没有这个条件达到这个水准；
 * 机器码可根据服务器的IP或者MAC地址自动生成，但是10位不够用；
 * 所以我们对算法稍微做了一点改动：
 * 1位符号位，永远为0；<br/>
 * 32位时间码，存储秒钟，可以自行设定一个开始时间，计算两者之间的秒数即可，可用136年；<br/>
 * 16为机器码，可部署2^16=65536个局点，且方便根据IP计算机器码，注意：此方式掩码必须为16，即IP只变后两位，前两位不便，否则可能会出现ID冲突问题；<br/>
 * 15位序号码，可生成2^15=32768个ID，1秒钟3万ID基本够用；<br/>
 */
@Slf4j
public class SnowflakeByIp implements Snowflake {

    private static final IdConfiguration config = new IdConfiguration();
    /**
     * 当前机器码
     */
    private static long machineCode = -1;
    /**
     * 最后时间码
     */
    private static long latestTimeCode = 0L;
    /**
     * 序号
     */
    private static int sequence = 0;

    @Override
    public long getMachineCode() {
        if (machineCode != -1) {
            return machineCode;
        }
        InetAddress addr = null;
        try {
            addr = HostUtils.getLanInetAddress();
        } catch (RuntimeException | SocketException e) {
            throw new IdException(SnowflakeByIp.class.getName() + "无法获取当前IP地址", e);
        }
        byte[] ip = addr.getAddress();
        int ipLen = 4;
        if (ip.length != ipLen) {
            throw new IdException(SnowflakeByIp.class.getName() + "不支持此IP:" + addr.getHostAddress());
        }
        log.info("IdGenerator's IP is: {}", addr.getHostAddress());

        machineCode = ((ip[2] & 0xff) << 8) | (ip[3] & 0xff);
        log.info("machineCode:{}", machineCode);
        return SnowflakeByIp.machineCode;
    }

    @Override
    public long getTimeCode() {
        long timestamp = System.currentTimeMillis() / 1000 - config.getMinTimestamp();
        if (timestamp < 0) {
            throw new IdException("系统时间错误，请修改服务器系统时间");
        }
        return timestamp;
    }

    @Override
    public int getSequenceCode() {
        sequence = (sequence + 1) & config.getSequenceMask();
        return sequence;
    }

    @Override
    public synchronized long nextId() {
        long timeCode = getTimeCode();
        long macCode = getMachineCode();

        if(timeCode > latestTimeCode) {
            sequence = 0;
            latestTimeCode = timeCode;
        } else {
            sequence = getSequenceCode();
            //如果seqCode已用完，循环获取下一个ID
            if(sequence == 0) {
                while(timeCode == latestTimeCode) {
                    timeCode = getTimeCode();
                }
                latestTimeCode = timeCode;
            }
        }
        return timeCode << config.getStampBit() | macCode << config.getSequenceBit() | sequence;
    }
}
